
#include <math.h>
#include "../iface/debug.h"
#include "../iface/testerror.h"
#include "../iface/APRInterface.h"
#include "../utils/Hash.h"
#include "Key.h"
#include "KeyDB.h"

#include "../crypt/ECDSAPublicKey.h"
#include "../crypt/ECDSAPrivateKey.h"
#include "../crypt/DSAPublicKey.h"
#include "../crypt/DSAPrivateKey.h"
#include "../crypt/RSAPublicKey.h"
#include "../crypt/RSAPrivateKey.h"
#include "../crypt/AESSymmetricKey.h"

#ifndef PRId64
#define PRId64 "ld"
#endif

/** The key purpose type names also double as the table names in the
 * key database. */
const char *Key::keypurposetypename[] = { "encryption_keys",
                                          "decryption_keys",
                                          "signing_keys",
                                          "checking_keys"    };

/** Constructs a lazy-bound key connected to the key database.
 *
 * @param ikdb The key database to connect to.
 * 
 * @param iname The key name/ID.
 * 
 * @param ithistype The key purpose type, either encryption,
 * decryption, signing or checking. */
Key::Key(KeyDB *ikdb, const char *iname, Key::keypurposetype ithistype)
{
   initNull();
   kdb = ikdb;
   if (!ikdb)
      errprint("key database is NULL");
   sqlite_key = new SQLiteTransactor(kdb, keypurposetypename[(int)(log2(ithistype))], "keyname", iname);
   thistype = ithistype;
}

/** Initialises the lazy-bound key from an abstract key. This is a
 * convenient way to write a key in usable cryptography format
 * straight to the database.
 * 
 * @param ikdb The key database pointer.
 * 
 * @param tkey The AbstractKey of any type.
 * 
 * @param pwd Password to encode the key with before creating the text
 * representation.
 * 
 * @param ithistype The purpose type of the key. */
void Key::initFromAbstractKey(KeyDB *ikdb, const AbstractKey &tkey, const char *pwd, Key::keypurposetype ithistype)
{
   initNull();
   kdb = ikdb;
   if (!ikdb)
      errprint("key database is NULL");
   sqlite_key = new SQLiteTransactor(kdb, keypurposetypename[(int)(log2(ithistype))], "keyname", tkey.getKeyName());
   setKeyType(tkey.getKeyTypeAbbrName());
   char *keytext = NULL;
   const char *npwd = pwd;
   if ((npwd) && (strlen(npwd) == 0)) npwd = NULL;
   //printf("PASSWORD (to text) IS: %s*\n", npwd);
   if (npwd)
      tkey.exportKeyToASCIIWithPassword(&keytext, npwd);
   else
      tkey.exportKeyToASCII(&keytext);
   setKeyData(keytext);
   StringUtils::razeString(&keytext);
}

/** Constructor for a database-writable instance of an encryption key.
 * 
 * @param ikdb Key database pointer.
 * 
 * @param tkey Encryption key in cryptographic format.
 * 
 * @param pwd Password for encoding the key into a protected text
 * format. */
Key::Key(KeyDB *ikdb, const EncryptionKey &tkey, const char *pwd)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->encryptionTable()->getWIPPassword();
   initFromAbstractKey(ikdb, tkey, npwd, KEYPURPOSE_ENCRYPTION);
}

/** Constructor for a database-writable instance of a decryption key.
 * 
 * @param ikdb Key database pointer.
 * 
 * @param tkey Decryption key in cryptographic format.
 * 
 * @param pwd Password for encoding the key into a protected text
 * format. */
Key::Key(KeyDB *ikdb, const DecryptionKey &tkey, const char *pwd)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->decryptionTable()->getWIPPassword();
   initFromAbstractKey(ikdb, tkey, npwd, KEYPURPOSE_DECRYPTION);
}

/** Constructor for a database-writable instance of a signing key.
 * 
 * @param ikdb Key database pointer.
 * 
 * @param tkey Signing key in cryptographic format.
 * 
 * @param pwd Password for encoding the key into a protected text
 * format. */
Key::Key(KeyDB *ikdb, const SigningKey &tkey, const char *pwd)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->signingTable()->getWIPPassword();
   initFromAbstractKey(ikdb, tkey, npwd, KEYPURPOSE_SIGNING);
}

/** Constructor for a database-writable instance of a checking key.
 * 
 * @param ikdb Key database pointer.
 * 
 * @param tkey Checking key in cryptographic format.
 * 
 * @param pwd Password for encoding the key into a protected text
 * format. */
Key::Key(KeyDB *ikdb, const CheckingKey &tkey, const char *pwd)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->checkingTable()->getWIPPassword();
   initFromAbstractKey(ikdb, tkey, npwd, KEYPURPOSE_CHECKING);
}

/** Key destructor. */
Key::~Key()
{
   if (sqlite_key) delete sqlite_key;
   sqlite_key = NULL;
}

/** Get the name of the key represented.
 * 
 * @return String representing name. */
const char *Key::getName()
{
   if (sqlite_key) return sqlite_key->getField("keyname");
   return NULL;
}

/** Get the purpose type ID of the key.
 * 
 * @return The purpose type ID of the key. */
const Key::keypurposetype Key::getPurposeType()
{
   return thistype;
}

/** Get the named purpose type of the key as a string. This is not
 * human readable as such, but the table name of the appropriate table
 * in the key database.
 * 
 * @return The named purpose of the key (database table name). */
const char *Key::getPurposeTypeName()
{
   return keypurposetypename[(int)(log2(thistype))];
}

SETGET_MACRO_AS_STRING(Key, KeyType, sqlite_key, "keytype")
SETGET_MACRO_AS_STRING(Key, KeyData, sqlite_key, "keydata")
SETGET_MACRO_AS_STRING(Key, Owner, sqlite_key, "owner")
SETGET_MACRO_AS_STRING(Key, KeyMark, sqlite_key, "keymark")
ADDRMV_MACRO_AS_CHARFLAGS(Key, KeyMark, sqlite_key, "keymark")
INCDEC_MACRO_AS_INT64(Key, UseReferenceCount, sqlite_key, "usedrefcount")
INCDEC_MACRO_AS_INT64(Key, ExistenceReferenceCount, sqlite_key, "existsrefcount")
SETGET_MACRO_AS_APRDATE(Key, DateCreated, sqlite_key, "rfc822date")
SETGET_MACRO_AS_STRING(Key, DateCreated, sqlite_key, "rfc822date")

/** Read-in and cache all parts of the key currently in the
 * database. */
void Key::precache()
{
   FATAL_IF_BLANK((void)0, getName(), "actor name");
   if (sqlite_key) sqlite_key->precache();
}

/** Write out to the database all parts of the key that have been
 * updated or set. */
bool Key::commit()
{
   if (sqlite_key == NULL) return false;
   sqlite_key->commit();
   return true;
}

/** Write out to the database all parts of the key that have been
 * updated or set. */
bool Key::update()
{
   if (sqlite_key == NULL) return false;
   sqlite_key->update();
   return true;
}

/** Set defaults, but no allocation. */
void Key::initNull()
{
   kdb = NULL;
}

/** Show any key open errors as readable text.
 * 
 * @param kname Key name.
 * 
 * @param tpur Key purpose.
 * 
 * @param terr Key open error code. */
void Key::showKeyOpenError(const char *kname, Key::keypurposetype tpur, Key::keyopenerror terr)
{
   const char *nkname = (kname) ? (kname) : "(null)";
   const char *npname = "unknown";
   switch (tpur)
   {
      case KEYPURPOSE_ENCRYPTION:
         npname = "encryption"; break;
      case KEYPURPOSE_DECRYPTION:
         npname = "decryption"; break;
      case KEYPURPOSE_SIGNING:
         npname = "signing"; break;
      case KEYPURPOSE_CHECKING:
         npname = "checking"; break;
      default: break;
   }
   switch (terr)
   {
      case KEY_OPEN_ERROR__INVALID_KEY_NAME:
         errprint("invalid key name for %s key", npname); break;
      case KEY_OPEN_ERROR__INVALID_KEY_DST:
         errprint("%s key '%s': invalid key destination", npname, nkname); break;
      case KEY_OPEN_ERROR__KEY_NOT_FOUND:
         errprint("%s key '%s': key not found", npname, nkname); break;
      case KEY_OPEN_ERROR__FAILED_SANITY:
         errprint("%s key '%s': sanity failed", npname, nkname); break;
      case KEY_OPEN_ERROR__KEY_IMPORT_FAILED:
         errprint("%s key '%s': key import failed", npname, nkname); break;
      case KEY_OPEN_ERROR__KEY_NULL:
         errprint("%s key '%s': key is NULL", npname, nkname); break;
      default: break;
   }
}

/** Gets a cryptographic format key using a password to decrypt a text
 * format into a usable key.
 * 
 * @param pwd Password to try to decrypt the key with.
 * 
 * @return A new abstract key. */
AbstractKey *Key::getKey(const char *pwd)
{
   keyopenerror terr;
   AbstractKey *abskey = getKey(pwd, &terr);
   showKeyOpenError(getName(), thistype, terr);
   return abskey;
}

/** Gets a cryptographic format key using a password to decrypt a text
 * format into a usable key.
 * 
 * @param pwd Password to try to decrypt the key with.
 * 
 * @param[out] terr Any error that occurs during the import from text
 * to usable key.
 * 
 * @return A new abstract key. */
AbstractKey *Key::getKey(const char *pwd, keyopenerror *terr)
{
   const char *tkeydata = getKeyData();
   if ((tkeydata == NULL) || (strlen(tkeydata) == 0))
   {
      if (terr)
         *terr =
            (tkeydata) ? KEY_OPEN_ERROR__KEY_NOT_FOUND : KEY_OPEN_ERROR__KEY_NULL;
      return NULL;
   }
   const char *tkeytype = getKeyType();
   const char *tkeyname = getName();
   const char *npwd = pwd;
   if ((npwd) && (strlen(npwd) == 0)) npwd = NULL;
   //printf("PASSWORD (from text) IS: %s*\n", npwd);
   AbstractKey *abskey = tryToGetKeyFromText(tkeytype, npwd, tkeydata, tkeyname, terr);
   return abskey;
}

/** Produce an encryption key in cryptographic format from the
 * database in one static call.
 * 
 * @param ikdb The key database pointer
 * 
 * @param pwd The password to decrypt the database key text data.
 * 
 * @param iname The name/ID of the key in the database.
 * 
 * @return The new encryption key. */
EncryptionKey *Key::getEncryptionKey(KeyDB *ikdb, const char *pwd, const char *iname)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->encryptionTable()->getWIPPassword();
   Key *nky = new Key(ikdb, iname, KEYPURPOSE_ENCRYPTION);
   AbstractKey *abskey = nky->getKey(npwd);
   delete nky;
   return dynamic_cast<EncryptionKey *>(abskey);
}

/** Produce a decryption key in cryptographic format from the database
 * in one static call.
 * 
 * @param ikdb The key database pointer
 * 
 * @param pwd The password to decrypt the database key text data.
 * 
 * @param iname The name/ID of the key in the database.
 * 
 * @return The new decryption key. */
DecryptionKey *Key::getDecryptionKey(KeyDB *ikdb, const char *pwd, const char *iname)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->decryptionTable()->getWIPPassword();
   Key *nky = new Key(ikdb, iname, KEYPURPOSE_DECRYPTION);
   AbstractKey *abskey = nky->getKey(npwd);
   delete nky;
   return dynamic_cast<DecryptionKey *>(abskey);
}

/** Produce a signing key in cryptographic format from the database in
 * one static call.
 * 
 * @param ikdb The key database pointer
 * 
 * @param pwd The password to decrypt the database key text data.
 * 
 * @param iname The name/ID of the key in the database.
 * 
 * @return The new signing key. */
SigningKey *Key::getSigningKey(KeyDB *ikdb, const char *pwd, const char *iname)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->signingTable()->getWIPPassword();
   Key *nky = new Key(ikdb, iname, KEYPURPOSE_SIGNING);
   AbstractKey *abskey = nky->getKey(npwd);
   delete nky;
   return dynamic_cast<SigningKey *>(abskey);
}

/** Produce a checking key in cryptographic format from the database
 * in one static call.
 * 
 * @param ikdb The key database pointer
 * 
 * @param pwd The password to decrypt the database key text data.
 * 
 * @param iname The name/ID of the key in the database.
 * 
 * @return The new checking key. */
CheckingKey *Key::getCheckingKey(KeyDB *ikdb, const char *pwd, const char *iname)
{
   const char *npwd = (pwd) ? (pwd) : ikdb->checkingTable()->getWIPPassword();
   Key *nky = new Key(ikdb, iname, KEYPURPOSE_CHECKING);
   AbstractKey *abskey = nky->getKey(npwd);
   delete nky;
   return dynamic_cast<CheckingKey *>(abskey);
}

/** Transform some generic key text representing a key into a full key
 * object.
 * 
 * @param keytype Some text describing the key type.
 * 
 * @param keypwd The password used to decrypt the key (if any).
 * 
 * @param keytext The actual body of text that makes up the encoded
 * key.
 * 
 * @param keyname A name to give to the key produced.
 * 
 * @param errpntr A pointer to put an error code into if the decoding
 * is unsuccessful.
 * 
 * @return The new AbstractKey object initialised from this text
 * data. */
AbstractKey *Key::tryToGetKeyFromText(const char *keytype, const char *keypwd, const char *keytext, const char *keyname, Key::keyopenerror *errpntr)
{
   int errval = 0;
   if (errpntr) *errpntr = KEY_OPEN_ERROR__NO_ERROR;
   if (strcmp(keytype, AbstractKey::key_type_abbrs[RSAPrivateKey::getStaticKeyType()]) == 0)
   {
      RSAPrivateKey *prvkey = new RSAPrivateKey(keyname);
      if (keypwd)
         errval = prvkey->importKeyFromASCIIWithPassword(keytext, keypwd, keyname);
      else
         errval = prvkey->importKeyFromASCII(keytext, keyname);
      if (errval)
      {
         delete prvkey;
         if (errpntr)
            *errpntr = KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
         return NULL;
      }
      return (AbstractKey *)prvkey;
   } else
   if (strcmp(keytype, AbstractKey::key_type_abbrs[RSAPublicKey::getStaticKeyType()]) == 0)
   {
      RSAPublicKey *pubkey = new RSAPublicKey(keyname);
      if (keypwd)
         errval = pubkey->importKeyFromASCIIWithPassword(keytext, keypwd, keyname);
      else
         errval = pubkey->importKeyFromASCII(keytext, keyname);
      if (errval)
      {
         delete pubkey;
         if (errpntr)
            *errpntr = KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
         return NULL;
      }
      return (AbstractKey *)pubkey;
   } else
   if (strcmp(keytype, AbstractKey::key_type_abbrs[DSAPrivateKey::getStaticKeyType()]) == 0)
   {
      DSAPrivateKey *prvkey = new DSAPrivateKey(keyname);
      if (keypwd)
         errval = prvkey->importKeyFromASCIIWithPassword(keytext, keypwd, keyname);
      else
         errval = prvkey->importKeyFromASCII(keytext, keyname);
      if (errval)
      {
         delete prvkey;
         if (errpntr)
            *errpntr = KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
         return NULL;
      }
      return (AbstractKey *)prvkey;
   } else
   if (strcmp(keytype, AbstractKey::key_type_abbrs[DSAPublicKey::getStaticKeyType()]) == 0)
   {
      DSAPublicKey *pubkey = new DSAPublicKey(keyname);
      if (keypwd)
         errval = pubkey->importKeyFromASCIIWithPassword(keytext, keypwd, keyname);
      else
         errval = pubkey->importKeyFromASCII(keytext, keyname);
      if (errval)
      {
         delete pubkey;
         if (errpntr)
            *errpntr = KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
         return NULL;
      }
      return (AbstractKey *)pubkey;
   } else
   if (strcmp(keytype, AbstractKey::key_type_abbrs[ECDSAPrivateKey::getStaticKeyType()]) == 0)
   {
      ECDSAPrivateKey *prvkey = new ECDSAPrivateKey(keyname);
      if (keypwd)
         errval = prvkey->importKeyFromASCIIWithPassword(keytext, keypwd, keyname);
      else
         errval = prvkey->importKeyFromASCII(keytext, keyname);
      if (errval)
      {
         delete prvkey;
         if (errpntr)
            *errpntr = KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
         return NULL;
      }
      return (AbstractKey *)prvkey;
   } else
   if (strcmp(keytype, AbstractKey::key_type_abbrs[ECDSAPublicKey::getStaticKeyType()]) == 0)
   {
      ECDSAPublicKey *pubkey = new ECDSAPublicKey(keyname);
      if (keypwd)
         errval = pubkey->importKeyFromASCIIWithPassword(keytext, keypwd, keyname);
      else
         errval = pubkey->importKeyFromASCII(keytext, keyname);
      if (errval)
      {
         delete pubkey;
         if (errpntr)
            *errpntr = KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
         return NULL;
      }
      return (AbstractKey *)pubkey;
   } else
   if (strcmp(keytype, AbstractKey::key_type_abbrs[AESSymmetricKey::getStaticKeyType()]) == 0)
   {
      AESSymmetricKey *symkey = new AESSymmetricKey(keyname);
      if (keypwd)
         errval = symkey->importKeyFromASCIIWithPassword(keytext, keypwd, keyname);
      else
         errval = symkey->importKeyFromASCII(keytext, keyname);
      if (errval)
      {
         delete symkey;
         if (errpntr)
            *errpntr = KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
         return NULL;
      }
      return (AbstractKey *)symkey;
   }
   if (errpntr)
      *errpntr = KEY_OPEN_ERROR__KEY_NOT_FOUND;
   return NULL;
}
